/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#ifndef simodo_loom_Feeder_template
#define simodo_loom_Feeder_template

/** 
 * @file Feeder_template.h
 * @brief Определения для Feeder_template
 */

#include <map>
#include <mutex>
#include <condition_variable>

namespace simodo::loom 
{
    /**
     * @brief Подача чего-либо для использования в производстве с учётом одновременного доступа.
     * 
     * @attention Обязательно используйте специальные безопасные упаковки! 
     * Податчик может испортиться, если в него пихать всякий хлам!
     * 
     * @note Доктор Борменталь: "Видимо речь идёт о потокобезопасной очереди (FIFO). 
     * Встречал такое в главе 6.2.2 Anthony Williams. C++ Concurrency in Action. Second Edition".
     * 
     * @note Профессор Преображенский: "Коллега, обратите внимание, здесь отсутствует использование
     * std::shared_ptr. Вероятно, изначальный вариант из указанной вами книги был несколько изменён".
     * 
     * @note Доктор Борменталь: "Вы правы, профессор! Похоже наш пациент начитался статей про потоковую
     * небезопасность std::shared_ptr, вроде вот этой: ["Разделяемые указатели и многопоточность. 
     * И снова о них, в который раз"](https://habr.com/ru/articles/311560/). И теперь боится использовать
     * этот замечательный класс".
     */
    template <typename T>
    class Feeder_template
    {
        std::multimap<int,T>         _data_queue;
        mutable std::mutex      _data_queue_mutex;
        std::condition_variable _data_cond;

    public:
        Feeder_template() {}

        void push(int weight, T new_value)
        {
            std::lock_guard<std::mutex> locking(_data_queue_mutex);

            _data_queue.insert({-weight,new_value});
            _data_cond.notify_one();
        }

        void wait_and_pop(T* value)
        {
            std::unique_lock<std::mutex> locking(_data_queue_mutex);

            _data_cond.wait(locking,[this]{return !_data_queue.empty();});

            auto it = _data_queue.begin();

            *value = it->second;
            _data_queue.erase(it);
        }

        bool try_pop(T* value)
        {
            std::lock_guard<std::mutex> locking(_data_queue_mutex);

            if (_data_queue.empty())
                return false;

            auto it = _data_queue.begin();
            
            *value = it->second;
            _data_queue.erase(it);

            return true;
        }

        bool empty() const
        {
            std::lock_guard<std::mutex> locking(_data_queue_mutex);

            return _data_queue.empty();
        }

        bool queued() const
        {
            std::lock_guard<std::mutex> locking(_data_queue_mutex);

            return _data_queue.size();
        }
    };

}

#endif // simodo_loom_Feeder_template
